import Decimal from "decimal.js";
import card, { cardSet, cardGet } from "./card";

type relation = '+' | '-' | '*' | '/' | '%' | 'round' | 'ceil' | 'floor' | '?:' | '??' | '>' | '<' | '==' | 'string' | 'get' | 'set'
let relationChecker = [
    '+', '-', '*', '/', '%', 'round', 'ceil', 'floor', '?:', '??', '>', '<', '==', 'string', 'get', 'set'
]
export type cardScript = {
    member: (cardScript | string | number | boolean )[];
    relation?: relation;
};
export function cardScriptRun(card: card, index: number[], getterValue?: cardScript,value?:string): (string | number | boolean) {
    if(!getterValue) return ''
    getterValue = {...getterValue}
    getterValue.member=getterValue.member.flatMap(v=>(value!=null&&v=="${value}")?value:v)
    // 选择枝下的let member不可以被提取到选择枝外因为部分选择枝下需要做特殊处理
    switch (getterValue.relation) {
        case "+": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                //console.log(out);
                
                return new Decimal(out)
            });
            //console.log( member.flatMap(v=>v.toNumber()));
            
            return member.reduce((total, value) => {
                return total.plus(value)
            }).toNumber();
        }
        case "-": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                return new Decimal(out)
            });
            return member.flatMap(v => new Decimal(v)).reduce((total, value) => {
                return total.minus(value)
            }).toNumber();
        }
        case "*": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                return new Decimal(out)
            });
            return member.reduce((total, value) => {
                return total.times(value)
            }).toNumber();
        }
        case "/": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                return new Decimal(out)
            });
            return (member[0].dividedBy(member[1])).toNumber()
        }
        case "%": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                return new Decimal(out)
            });
            return (member[0].mod(member[1])).toNumber();
        }
        case "round": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                return new Decimal(out)
            });
            return member[0].round().toNumber()
        }
        case "ceil": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                return new Decimal(out)
            });
            return member[0].ceil().toNumber()
        }
        case "floor": {
            let member: Decimal[] = getterValue.member.flatMap(m => {
                let out = m instanceof Object ? cardScriptRun(card, index, m , value) : m
                switch (typeof out) {
                    case "string": out = parseFloat(out);break
                    case "boolean": out = out ? 1 : 0;break
                }
                return new Decimal(out)
            });
            return member[0].floor().toNumber()
        }
        case "?:": {
            return (getterValue.member[0] instanceof Object ? cardScriptRun(card, index, getterValue.member[0]  , value) : getterValue.member[0]) ? (getterValue.member[1] instanceof Object ? cardScriptRun(card, index, getterValue.member[1] , value) : getterValue.member[1]) : (getterValue.member[2] instanceof Object ? cardScriptRun(card, index, getterValue.member[2] , value) : getterValue.member[2]);
        }
        case "??": {
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            return member[0] ?? member[1]
        }
        case ">": {
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            return member[0] > member[1];
        }
        case "<": {
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            return member[0] < member[1];
        }
        case "==": {
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            return member[0] == member[1];
        }
        case "string": {
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            return member.reduce((total, value) => {
                return total.toString() + value.toString();
            });
        }
        case "get": {
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            return cardGet(card, member[0].toString(), index) ?? '';
        }
        case "set": {
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            cardSet(card, member[0].toString(), member[1].toString(), index);
            return member[1];
        }
        case undefined:{
            let member: (string | number | boolean)[] = getterValue.member.flatMap(m => { return m instanceof Object ? cardScriptRun(card, index, m , value) : m; });
            return member[0]
        }
        default: {
            return ''
        }
    }
}
/**
 * 用字符串的形式构造cardScript
 * 
 * 字符串的格式为<参与值> [$]<运算符> [参与值] [参与值] [参与值]...
 * 
 * 注意空格,字符串通过空格分隔值
 * 
 * 如果你在写一个setter专用的cardScript，使用${value}作为参与值，它表示用户输入的值,在非setter的cardScript中${value}不会被识别,如果你需要在setter专用的
 * cardScript中表示'${value}'而不是用户输入值的话（说真的为什么要在游戏中出现${value}这样的字样），请将${value}拆分并用 string 操作符拼接出来，例如
 * ( ${ $string value})，只有恰好为${value}的参与值会被识别
 * 
 * 如果你的参与值包含空格，'('或者')'以及其他的占用符，请用方括号包裹那个值
 * 
 * 当你的值内需要方括号时，使用'$['和'$]'来作为转义符使用
 * 
 * 运算符前的'$'是可选的，无论何时都只会把第二个值当作运算符，但有助于你在一个复杂字符串中区分运算符和可选值，
 * 
 * 参与值也可以用"()"包裹来表示toGetvalue字符串，借此构造嵌套式的复杂运算
 * 
 * 示例：
 *  * `10 + 5` 表示返回10+5的值，（仅作为示例，实际操作时请直接写作15而不是调用getvalue）
 *  * `15 $- (hp $get)` 表示从卡面获取hp，并用15减去他
 *  * `(hp $get) $+ ((mp$get) * 1.5)` 表示从卡面获取hp，获取mp并乘以1.5再把它们相加
 *  * `$[ $string [( ] (hp $get) [ )] $]` 表示从卡面获取hp,并用括号包裹它，再用方括号包裹它
 * 
 * 可用的运算符：
 * 
 * '+' | '-' | '*' | '/' | '%' | 'round' | 'ceil' | 'floor' | '?:' | '??' | '>' | '<' | '==' | 'string' | 'get' | 'set'
 * 
 * ps:没有被提及的参与值会被忽略，但仍然会被计算，'?:'操作符除外
 * * '+'        将所有参与值相加
 * * '-'        将所有参与值相加，第一个值作为被减数，其余的都作为减数
 * * '*'        将所有参与值相乘
 * * '/'        将前两个参与值相除，第一个值作为被除数，第二个作为除数,其余的值会被忽略
 * * '%'        将前两个参与值取余，第一个值作为被除数，第二个作为除数,其余的值会被忽略
 * * 'round'    将第一个参与值四舍五入
 * * 'ceil'     将第一个参与值向上取整
 * * 'floor'    将第一个参与值向下取整
 * * '?:'       如果第一个参与值为true则计算并返回第二个参与值，为false则计算并返回第三个参与值 
 * * '??'       如果第一个参与值不为空则返回第一个参与值，否则返回第二个参与值
 * * '>'        如果第一个参与值大于第二个参与值则返回true，否则返回false，请注意两个操作数最好都为数字
 * * '<'        如果第一个参与值小于第二个参与值则返回true，否则返回false，请注意两个操作数最好都为数字
 * * '=='       如果前两个参与值相等则返回true，否则返回false
 * * 'string'   将所有参与值作为字符拼接
 * * 'get'      以第一个参与值为key从卡面查找数据
 * * 'set'      将卡面第一个参与值对应的key赋值为第二个参与值
 */
export function cardScript(input: string): cardScript {
    const checkedString = input.trim();
    const readingString = { string: checkedString };
    let out =readGetterValue(readingString)
    //console.log(out);
    return out
}
function readGetterValue(reading: { string: string }): cardScript {
    let readedValues: (cardScript | (string | number | boolean))[] = []
    let readedRelation: relation | undefined = undefined
    let index = 0
    while (reading.string.length !== 0) {
        reading.string = reading.string.trim();
        if (reading.string.charAt(0) === ')') {
            reading.string=reading.string.replace(')','')
            break;
        }
        if (index !== 1) {
            let value = readValue(reading)
            if( typeof value == 'string')value=value.replace(/\$(?=\])/g,'')
            readedValues.push(value)
        } else {
            readedRelation = readRelation(reading)
        }
        index++
    }
    if (readedRelation == undefined) {
        console.error('未找到运算符，请确保每个算式都有至少两个值，已自动匹配运算符为+');
        readedRelation = '+'
    }
    return {
        member: readedValues,
        relation: readedRelation
    }
}
function readValue(reading: { string: string }): (cardScript | (string | number | boolean)){
    switch (reading.string.charAt(0)) {
        case "(": {
            reading.string = reading.string.slice(1);
            return readGetterValue(reading)
        }
        case "[": {
            reading.string = reading.string.slice(1);
            let value = reading.string.match(/.*?[^$](?=\])/)?.[0]?? ''
            reading.string = reading.string.replace(/.*?[^$]\]/, '')
            return value 
        }
        default: {
            const defaultValue = reading.string.match(/^[^\s\)]*/)?.[0] ?? '';
            reading.string = reading.string.replace(/^[^\s\)]*/, '')
            return defaultValue 
        }
    }
}
function readRelation(reading: { string: string }):relation|undefined {
    reading.string=reading.string.replace(/^\$/,'')
    let value = reading.string.match(/^[^\s\)]*/)?.[0] ?? '';
    reading.string = reading.string.replace(/^[^\s\)]*/, '')
    if (relationChecker.includes(value)) {
        return value as relation
    } else {
        console.log(reading)
        console.error('未找到运算符，请确保每个算式都有至少两个值，已自动匹配运算符为+');
        return '+'
    }
}

//utils
/**
 * 快速生成用于卡面基础值数据的cardScript
 */
export function baseVaule(key:string,baseValue:{key:string,coefficient:number}[]):{getValue:cardScript,setValue:cardScript}{
    let baseValueCardScript:cardScript={
        member:[],
        relation:'+'
    }
    baseValue.forEach(v=>{
        baseValueCardScript.member.push( cardScript(`(${v.key} $get) * ${v.coefficient}`))
    })
    return {
        getValue:{
            member:[baseValueCardScript,cardScript(`${key} $get`)],
            relation:'+' 
        },
        setValue:{
            member:[key,{
                member:["${value}",baseValueCardScript],
                relation:'-'
            }],
            relation:'set'
        }
    }

}